import FreeCAD

class Vector(object):
    """Create a 3-dimensional vector

        **you can provide any of:**

        * nothing (in which case a zero vector is return)
        * a :class:`FreeCAD.Base.Vector` instance to wrap
        * a :class:`Vector` (in which case it is copied)
        * a 2-tuple of :class:`float`, ``(x, y)`` (z=0)
        * a 3-tuple of :class:`float`, ``(x, y, z)``
        * 2 :class:`float` values, as ``x``, and ``y``
        * 3 :class:`float` values, as ``x``, ``y``, and ``z``
        * named coordinates, like ``Vector(x=1, z=2)``

    """
    def __init__(self, *args, **kwargs):
        fV = None

        if args and not kwargs:
            if (1 <= len(args) <= 3):  # 1, 2, or 3 arguments (no keyword arguments given)
                if isinstance(args[0], (int, float)):
                    # First argument is a number, pass list through to freecad
                    fV = FreeCAD.Base.Vector(*args)
                elif len(args) == 1:
                    arg = args[0]  # only 1 argument given
                    if isinstance(arg, Vector):
                        v = arg.wrapped
                        fV = FreeCAD.Base.Vector(v.x, v.y, v.z)  # create copy
                    elif isinstance(arg, FreeCAD.Base.Vector):
                        v = arg
                        fV = FreeCAD.Base.Vector(v.x, v.y, v.z)  # create copy
                    elif isinstance(arg, (list, tuple)):
                        if (1 <= len(arg) <= 3):
                            fV = FreeCAD.Base.Vector(*arg)
        elif not args:
            if set(kwargs.keys()) - set('xyz'):
                pass  # kwargs contains invalid arguments, raise exception
            else:
                fV = FreeCAD.Base.Vector(
                    kwargs.get('x', 0),
                    kwargs.get('y', 0),
                    kwargs.get('z', 0),
                )

        if fV is None:
            raise ValueError(
                "Expected 2 or 3 floats, FreeCAD Vector, 2 or 3-tuple, or named lower-case coords. " + \
                ("%r %r" % (args, kwargs))
            )

        self._wrapped = fV

    @property
    def x(self):
        return self._wrapped.x

    @x.setter
    def x(self, value):
        self._wrapped.x = value

    @property
    def y(self):
        return self._wrapped.y

    @y.setter
    def y(self, value):
        self._wrapped.y = value

    @property
    def z(self):
        return self._wrapped.z

    @z.setter
    def z(self, value):
        self._wrapped.z = value

    @property
    def Length(self):
        return self._wrapped.Length

    @property
    def wrapped(self):
        return self._wrapped

    def toTuple(self):
        v = self._wrapped
        return (v.x, v.y, v.z)

    # TODO: is it possible to create a dynamic proxy without all this code?
    def cross(self, v):
        return type(self)(self._wrapped.cross(v.wrapped))

    def dot(self, v):
        return self._wrapped.dot(v.wrapped)

    def sub(self, v):
        return type(self)(self._wrapped.sub(v.wrapped))

    def add(self, v):
        return type(self)(self._wrapped.add(v.wrapped))

    def multiply(self, scale):
        """Return a copy multiplied by the provided scalar"""
        tmp_fc_vector = FreeCAD.Base.Vector(self._wrapped)
        return type(self)(tmp_fc_vector.multiply(scale))

    def normalized(self):
        """Return a normalized version of this vector"""
        tmp_fc_vector = FreeCAD.Base.Vector(self._wrapped)
        tmp_fc_vector.normalize()
        return type(self)(tmp_fc_vector)

    def Center(self):
        """Return the vector itself

        The center of myself is myself.
        Provided so that vectors, vertexes, and other shapes all support a
        common interface, when Center() is requested for all objects on the
        stack.
        """
        return self

    def getAngle(self, v):
        return self._wrapped.getAngle(v.wrapped)

    def distanceToLine(self):
        raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")

    def projectToLine(self):
        raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")

    def distanceToPlane(self):
        raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")

    def projectToPlane(self):
        raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")

    def __bool__(self):
        return any(coord != 0 for coord in self.toTuple())

    __nonzero__ = __bool__  # python 2.x compatability

    def __add__(self, v):
        return self.add(v)

    def __sub__(self, v):
        return self.sub(v)

    def __mul__(self, scalar):
        return self.multiply(scalar)

    def __truediv__(self, scalar):
        if scalar == 0:
            raise ZeroDivisionError("%r division by zero" % (type(self)))
        return self.multiply(1. / scalar)

    def __neg__(self):
        return type(self)(self.wrapped.negative())

    def __abs__(self):
        return self.Length

    def __repr__(self):
        return self._wrapped.__repr__()

    def __str__(self):
        return self._wrapped.__str__()

    def __ne__(self, other):
        if isinstance(other, Vector):
            return self.wrapped.__ne__(other.wrapped)
        return False

    def __eq__(self, other):
        if isinstance(other, Vector):
            return self.wrapped.__eq__(other.wrapped)
        return False

    def __copy__(self):
        return type(self)(self._wrapped)
